package com.mqz.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mqz.seckill.config.RequestLimit;
import com.mqz.seckill.pojo.SeckillMessage;
import com.mqz.seckill.pojo.SeckillOrder;
import com.mqz.seckill.pojo.User;
import com.mqz.seckill.rabbitmq.RabbitProducer;
import com.mqz.seckill.service.GoodsService;
import com.mqz.seckill.service.OrderService;
import com.mqz.seckill.service.SeckillOrderService;
import com.mqz.seckill.vo.GoodsVO;
import com.mqz.seckill.vo.RespBean;
import com.mqz.seckill.vo.RespBeanEnum;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private GoodsService goodsService;
    @Resource
    private OrderService orderService;
    @Resource
    private SeckillOrderService seckillOrderService;
    @Resource
    private RabbitProducer rabbitProducer;
    @Resource
    private RedisScript<Long> releaseLockScript;

    private Map<Long, Boolean> emptyStockMap = new HashMap<>();

    @RequestMapping("/{token}/doSeckill")
    @ResponseBody
    public RespBean doSeckill(@PathVariable String token, Model model, User user, Long goodsId) {

        //验证用户登录
        if (user == null) {
//            return "login";
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }

        //验证秒杀token
        if (!orderService.isTokenLegal(user, goodsId, token)) {

            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }

        //db获取goodsVO
        GoodsVO goodsVO = goodsService.getGoodsVOByGoodsId(goodsId);
        if (goodsVO.getStockCount() < 1) {
//            model.addAttribute("errMsg", RespBeanEnum.EMPTY_STOCK_ERROR.getMessage());
//            return "seckillError";

            return RespBean.error(RespBeanEnum.EMPTY_STOCK_ERROR);
        }

        //校验用户复购
        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue()
                .get("order:" + user.getId() + ":" + goodsId);
        if (seckillOrder != null) {
//            model.addAttribute("errMsg", RespBeanEnum.REPEATED_PURCHASE_ERROR.getMessage());
//            return "seckillError";

            return RespBean.error(RespBeanEnum.REPEATED_PURCHASE_ERROR);
        } else {
            if (seckillOrderService.getOne(
                    new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).
                            eq("goods_id", goodsId)) != null) {
//                model.addAttribute("errMsg", RespBeanEnum.REPEATED_PURCHASE_ERROR.getMessage());
//                return "seckillError";

                return RespBean.error(RespBeanEnum.REPEATED_PURCHASE_ERROR);
            }
        }

        //内存标记阻挡进程
        if (emptyStockMap.get(goodsId)) {
//            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
//            model.addAttribute("errMsg", RespBeanEnum.EMPTY_STOCK_ERROR.getMessage());
//            return "seckillError";

            return RespBean.error(RespBeanEnum.EMPTY_STOCK_ERROR);
        }

        //方案一：redis预减库存，具有原子性，当库存小于0时阻挡进程，直接返回，可解决超卖问题
//        Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
//        if (decrement < 0) {
//            //内存标记
//            emptyStockMap.put(goodsId, true);
//
//            redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
//
////            model.addAttribute("errMsg", RespBeanEnum.EMPTY_STOCK_ERROR.getMessage());
////            return "seckillError";
//
//            return RespBean.error(RespBeanEnum.EMPTY_STOCK_ERROR);
//        }
        //方案二：使用redis分布式锁，拓展多次操作的隔离范围
        String uuid = UUID.randomUUID().toString();

        //获取锁
        Boolean hasLock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);

        if (hasLock) {

            //创建释放锁脚本(在config注入，实现解耦)
//            String scriptText = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//            redisScript.setScriptText(scriptText);
//            redisScript.setResultType(Long.class);

            Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
            if (decrement < 0) {
                //内存标记该商品库存为空
                emptyStockMap.put(goodsId, true);

                redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);

                //释放锁，使用Lua脚本
                redisTemplate.execute(releaseLockScript, Arrays.asList("lock"), uuid);

//            model.addAttribute("errMsg", RespBeanEnum.EMPTY_STOCK_ERROR.getMessage());
//            return "seckillError";

                return RespBean.error(RespBeanEnum.EMPTY_STOCK_ERROR);
            }

            //释放锁，使用Lua脚本
            redisTemplate.execute(releaseLockScript, Arrays.asList("lock"), uuid);

        } else {

            return RespBean.error(RespBeanEnum.PURCHASE_FAILED_ERROR);
        }

//
//        //db秒杀
//        Order order = orderService.seckill(user, goodsVO);
//        if (order == null) {
//            model.addAttribute("errMsg", RespBeanEnum.UNKNOWN_PURCHASE_ERROR.getMessage());
//
//            return "secKillError";
//        }
//
//        model.addAttribute("order", order);
//        model.addAttribute("goods", goodsVO);

        //使用消息队列,快速返回
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        rabbitProducer.produce(JSONUtil.toJsonStr(seckillMessage));

//        model.addAttribute("errMsg", RespBeanEnum.WAITING.getMessage());
//        return "seckillWaiting";

        return RespBean.error(RespBeanEnum.WAITING);
    }

    @RequestMapping("/token")
    @ResponseBody
    @RequestLimit(timeout = 5, maxCount = 5, requireLogin = true)
    public RespBean getToken(User user, Long goodsId, String captcha, HttpServletRequest request) {

        if (user == null || goodsId < 0) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }

        //限流防刷
//        String uri = request.getRequestURI();
//
//        ValueOperations ops = redisTemplate.opsForValue();
//        Integer requestCount = (Integer) ops.get(uri + ":" + user.getId());
//        String key = uri + ":" + user.getId();
//
//        if (requestCount == null) {
//            ops.set(key, 1, 5, TimeUnit.SECONDS);
//        } else if (requestCount < 5) {
//            ops.increment(key);
//        } else {
//            return RespBean.error(RespBeanEnum.OVER_REQUEST_ERROR);
//        }

        //校验验证码
        if (!orderService.isCaptchaLegal(user, goodsId, captcha)) {
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }

        return RespBean.success(orderService.createToken(user, goodsId));
    }

    //创建验证码
    @RequestMapping("/captcha")
    public void creatCaptcha(HttpServletRequest request, HttpServletResponse response, User user, Long goodsId) {
        orderService.creatCaptcha(request, response, user, goodsId);

        //考虑项目分布式，保存到redis
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId, request.getSession().getAttribute("happy-captcha"), 60, TimeUnit.SECONDS);
    }

    @RequestMapping("/verifyCaptcha")
    @ResponseBody
    public boolean verifyCaptcha(User user, Long goodsId, String captcha) {
        return orderService.isCaptchaLegal(user, goodsId, captcha);
    }

    //在类的属性初始化后自动执行
    @Override
    public void afterPropertiesSet() throws Exception {

        //redis预设库存
        List<GoodsVO> list = goodsService.GoodsVOList();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(goodsVO -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVO.getId(), goodsVO.getStockCount());

            emptyStockMap.put(goodsVO.getId(), false);
        });
    }
}

